#include <types.h>
#include <x86.h>
#include <stdio.h>
#include <string.h>

/* stupid I/O delay routine necessitated by historical PC design flaws */
static void
delay(void) {
	inb(0x84);
	inb(0x84);
	inb(0x84);
	inb(0x84);
}

/***** Serial I/O code *****/
#define COM1			0x3F8

#define COM_RX			0		// In:  Receive buffer (DLAB=0)
#define COM_TX			0		// Out: Transmit buffer (DLAB=0)
#define COM_DLL			0		// Out: Divisor Latch Low (DLAB=1)
#define COM_DLM			1		// Out: Divisor Latch High (DLAB=1)
#define COM_IER			1		// Out: Interrupt Enable Register
#define COM_IER_RDI		0x01	// Enable receiver data interrupt
#define COM_IIR			2		// In:  Interrupt ID Register
#define COM_FCR			2		// Out: FIFO Control Register
#define COM_LCR			3		// Out: Line Control Register
#define COM_LCR_DLAB	0x80	// Divisor latch access bit
#define COM_LCR_WLEN8	0x03	// Wordlength: 8 bits
#define COM_MCR			4		// Out: Modem Control Register
#define COM_MCR_RTS		0x02	// RTS complement
#define COM_MCR_DTR		0x01	// DTR complement
#define COM_MCR_OUT2	0x08	// Out2 complement
#define COM_LSR			5		// In:  Line Status Register
#define COM_LSR_DATA	0x01	// Data available
#define COM_LSR_TXRDY	0x20	// Transmit buffer avail
#define COM_LSR_TSRE	0x40	// Transmitter off

#define MONO_BASE		0x3B4
#define MONO_BUF		0xB0000
#define CGA_BASE		0x3D4
#define CGA_BUF 		0xB8000
#define CRT_ROWS		25
#define CRT_COLS		80
#define CRT_SIZE		(CRT_ROWS * CRT_COLS)

#define LPTPORT			0x378

static uint16_t *crt_buf;
static uint16_t crt_pos;
static uint16_t addr_6845;

/* TEXT-mode CGA/VGA display output */

static void
cga_init(void) {
	volatile uint16_t *cp = (uint16_t *)CGA_BUF;
	uint16_t was = *cp;
	*cp = (uint16_t) 0xA55A;
	if (*cp != 0xA55A) {
		cp = (uint16_t*)MONO_BUF;
		addr_6845 = MONO_BASE;
	} else {
		*cp = was;
		addr_6845 = CGA_BASE;
	}

	// Extract cursor location
	uint32_t pos;
	outb(addr_6845, 14);
	pos = inb(addr_6845 + 1) << 8;
	outb(addr_6845, 15);
	pos |= inb(addr_6845 + 1);

	crt_buf = (uint16_t*) cp;
	crt_pos = pos;
}

static bool serial_exists = 0;

static void
serial_init(void) {
	// Turn off the FIFO
	outb(COM1 + COM_FCR, 0);

	// Set speed; requires DLAB latch
	//Line Control Register, set Divisor latch access bit
	outb(COM1 + COM_LCR, COM_LCR_DLAB);  				//outb(0x03FB, 0x80)
	//Divisor Latch Low (DLAB=1)
	outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600));	//outb(0x03F8, 12)
	//Divisor Latch Low (DLAB=1)
	outb(COM1 + COM_DLM, 0);

	// 8 data bits, 1 stop bit, parity off; turn off DLAB latch
	outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB);

	// No modem controls
	outb(COM1 + COM_MCR, 0);
	// Enable rcv interrupts
	outb(COM1 + COM_IER, COM_IER_RDI);

	// Clear any preexisting overrun indications and interrupts
	// Serial port doesn't exist if COM_LSR returns 0xFF
	serial_exists = (inb(COM1 + COM_LSR) != 0xFF);

	(void) inb(COM1+COM_IIR);
	(void) inb(COM1+COM_RX);
}

/* lpt_putc - copy console output to parallel port */
static void
lpt_putc(int c) {
	int i;
	for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) {
		delay();
	}
	outb(LPTPORT + 0, c);
	outb(LPTPORT + 2, 0x08 | 0x04 | 0x01);
	outb(LPTPORT + 2, 0x08);
}

/* cga_putc - print character to console */
static void
cga_putc(int c) {
	// set black on white
	if (!(c & ~0xFF)) {
		c |= 0x0700;
	}

	switch (c & 0xff) {
	case '\b':
		if (crt_pos > 0) {
			crt_pos --;
			crt_buf[crt_pos] = (c & ~0xff) | ' ';
		}
		break;
	case '\n':
		crt_pos += CRT_COLS;
	case '\r':
		crt_pos -= (crt_pos % CRT_COLS);
		break;
	default:
		crt_buf[crt_pos ++] = c;     // write the character
		break;
	}

	// What is the purpose of this?
	if (crt_pos >= CRT_SIZE) {
		int i;
		memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
		for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) {
			crt_buf[i] = 0x0700 | ' ';
		}
		crt_pos -= CRT_COLS;
	}

	// move that little blinky thing
	outb(addr_6845, 14);
	outb(addr_6845 + 1, crt_pos >> 8);
	outb(addr_6845, 15);
	outb(addr_6845 + 1, crt_pos);
}

/* serial_putc - print character to serial port */
static void
serial_putc(int c) {
    int i;
	for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) {
		delay();
	}
	outb(COM1 + COM_TX, c);
}

/* cons_init - initializes the console devices */
void
cons_init(void) {
	cga_init();
	serial_init();
	if (!serial_exists) {
		cprintf("serial port does not exist!!\n");
	}
}

/* cons_putc - print a single character @c to console devices */
void
cons_putc(int c) {
	lpt_putc(c);
	cga_putc(c);
	serial_putc(c);
}

